{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "e2ff5745",
      "metadata": {
        "id": "e2ff5745"
      },
      "source": [
        "## Install (Colab Only)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "144e9085-f4a6-499d-bcaf-63357dc7c752",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:15:27.529983566Z",
          "start_time": "2023-07-03T16:15:27.526086345Z"
        },
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "144e9085-f4a6-499d-bcaf-63357dc7c752",
        "jp-MarkdownHeadingCollapsed": true,
        "outputId": "be18ddfc-fc8c-433b-8389-7bb1f2efc9aa"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Cloning into 'PyEPO'...\n",
            "remote: Enumerating objects: 146, done.\u001b[K\n",
            "remote: Counting objects: 100% (146/146), done.\u001b[K\n",
            "remote: Compressing objects: 100% (132/132), done.\u001b[K\n",
            "remote: Total 146 (delta 29), reused 67 (delta 9), pack-reused 0 (from 0)\u001b[K\n",
            "Receiving objects: 100% (146/146), 6.93 MiB | 21.13 MiB/s, done.\n",
            "Resolving deltas: 100% (29/29), done.\n",
            "Processing ./PyEPO/pkg\n",
            "  Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
            "Requirement already satisfied: numpy in /usr/local/lib/python3.11/dist-packages (from pyepo==1.0.0) (1.26.4)\n",
            "Requirement already satisfied: scipy in /usr/local/lib/python3.11/dist-packages (from pyepo==1.0.0) (1.13.1)\n",
            "Collecting pathos (from pyepo==1.0.0)\n",
            "  Downloading pathos-0.3.3-py3-none-any.whl.metadata (11 kB)\n",
            "Requirement already satisfied: tqdm in /usr/local/lib/python3.11/dist-packages (from pyepo==1.0.0) (4.67.1)\n",
            "Collecting configspace (from pyepo==1.0.0)\n",
            "  Downloading configspace-1.2.1.tar.gz (130 kB)\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m131.0/131.0 kB\u001b[0m \u001b[31m4.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25h  Installing build dependencies ... \u001b[?25l\u001b[?25hdone\n",
            "  Getting requirements to build wheel ... \u001b[?25l\u001b[?25hdone\n",
            "  Preparing metadata (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n",
            "Requirement already satisfied: scikit_learn in /usr/local/lib/python3.11/dist-packages (from pyepo==1.0.0) (1.6.1)\n",
            "Requirement already satisfied: torch>=1.13.1 in /usr/local/lib/python3.11/dist-packages (from pyepo==1.0.0) (2.5.1+cu124)\n",
            "Requirement already satisfied: filelock in /usr/local/lib/python3.11/dist-packages (from torch>=1.13.1->pyepo==1.0.0) (3.17.0)\n",
            "Requirement already satisfied: typing-extensions>=4.8.0 in /usr/local/lib/python3.11/dist-packages (from torch>=1.13.1->pyepo==1.0.0) (4.12.2)\n",
            "Requirement already satisfied: networkx in /usr/local/lib/python3.11/dist-packages (from torch>=1.13.1->pyepo==1.0.0) (3.4.2)\n",
            "Requirement already satisfied: jinja2 in /usr/local/lib/python3.11/dist-packages (from torch>=1.13.1->pyepo==1.0.0) (3.1.5)\n",
            "Requirement already satisfied: fsspec in /usr/local/lib/python3.11/dist-packages (from torch>=1.13.1->pyepo==1.0.0) (2024.10.0)\n",
            "Collecting nvidia-cuda-nvrtc-cu12==12.4.127 (from torch>=1.13.1->pyepo==1.0.0)\n",
            "  Downloading nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)\n",
            "Collecting nvidia-cuda-runtime-cu12==12.4.127 (from torch>=1.13.1->pyepo==1.0.0)\n",
            "  Downloading nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)\n",
            "Collecting nvidia-cuda-cupti-cu12==12.4.127 (from torch>=1.13.1->pyepo==1.0.0)\n",
            "  Downloading nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl.metadata (1.6 kB)\n",
            "Collecting nvidia-cudnn-cu12==9.1.0.70 (from torch>=1.13.1->pyepo==1.0.0)\n",
            "  Downloading nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl.metadata (1.6 kB)\n",
            "Collecting nvidia-cublas-cu12==12.4.5.8 (from torch>=1.13.1->pyepo==1.0.0)\n",
            "  Downloading nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)\n",
            "Collecting nvidia-cufft-cu12==11.2.1.3 (from torch>=1.13.1->pyepo==1.0.0)\n",
            "  Downloading nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)\n",
            "Collecting nvidia-curand-cu12==10.3.5.147 (from torch>=1.13.1->pyepo==1.0.0)\n",
            "  Downloading nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)\n",
            "Collecting nvidia-cusolver-cu12==11.6.1.9 (from torch>=1.13.1->pyepo==1.0.0)\n",
            "  Downloading nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl.metadata (1.6 kB)\n",
            "Collecting nvidia-cusparse-cu12==12.3.1.170 (from torch>=1.13.1->pyepo==1.0.0)\n",
            "  Downloading nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl.metadata (1.6 kB)\n",
            "Requirement already satisfied: nvidia-nccl-cu12==2.21.5 in /usr/local/lib/python3.11/dist-packages (from torch>=1.13.1->pyepo==1.0.0) (2.21.5)\n",
            "Requirement already satisfied: nvidia-nvtx-cu12==12.4.127 in /usr/local/lib/python3.11/dist-packages (from torch>=1.13.1->pyepo==1.0.0) (12.4.127)\n",
            "Collecting nvidia-nvjitlink-cu12==12.4.127 (from torch>=1.13.1->pyepo==1.0.0)\n",
            "  Downloading nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)\n",
            "Requirement already satisfied: triton==3.1.0 in /usr/local/lib/python3.11/dist-packages (from torch>=1.13.1->pyepo==1.0.0) (3.1.0)\n",
            "Requirement already satisfied: sympy==1.13.1 in /usr/local/lib/python3.11/dist-packages (from torch>=1.13.1->pyepo==1.0.0) (1.13.1)\n",
            "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /usr/local/lib/python3.11/dist-packages (from sympy==1.13.1->torch>=1.13.1->pyepo==1.0.0) (1.3.0)\n",
            "Requirement already satisfied: pyparsing in /usr/local/lib/python3.11/dist-packages (from configspace->pyepo==1.0.0) (3.2.1)\n",
            "Requirement already satisfied: more_itertools in /usr/local/lib/python3.11/dist-packages (from configspace->pyepo==1.0.0) (10.6.0)\n",
            "Collecting ppft>=1.7.6.9 (from pathos->pyepo==1.0.0)\n",
            "  Downloading ppft-1.7.6.9-py3-none-any.whl.metadata (12 kB)\n",
            "Collecting dill>=0.3.9 (from pathos->pyepo==1.0.0)\n",
            "  Downloading dill-0.3.9-py3-none-any.whl.metadata (10 kB)\n",
            "Collecting pox>=0.3.5 (from pathos->pyepo==1.0.0)\n",
            "  Downloading pox-0.3.5-py3-none-any.whl.metadata (8.0 kB)\n",
            "Collecting multiprocess>=0.70.17 (from pathos->pyepo==1.0.0)\n",
            "  Downloading multiprocess-0.70.17-py311-none-any.whl.metadata (7.2 kB)\n",
            "Requirement already satisfied: joblib>=1.2.0 in /usr/local/lib/python3.11/dist-packages (from scikit_learn->pyepo==1.0.0) (1.4.2)\n",
            "Requirement already satisfied: threadpoolctl>=3.1.0 in /usr/local/lib/python3.11/dist-packages (from scikit_learn->pyepo==1.0.0) (3.5.0)\n",
            "Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.11/dist-packages (from jinja2->torch>=1.13.1->pyepo==1.0.0) (3.0.2)\n",
            "Downloading nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl (363.4 MB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m363.4/363.4 MB\u001b[0m \u001b[31m4.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl (13.8 MB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m13.8/13.8 MB\u001b[0m \u001b[31m102.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl (24.6 MB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m24.6/24.6 MB\u001b[0m \u001b[31m81.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl (883 kB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m883.7/883.7 kB\u001b[0m \u001b[31m50.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl (664.8 MB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m664.8/664.8 MB\u001b[0m \u001b[31m2.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl (211.5 MB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m211.5/211.5 MB\u001b[0m \u001b[31m5.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl (56.3 MB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m56.3/56.3 MB\u001b[0m \u001b[31m13.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl (127.9 MB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m127.9/127.9 MB\u001b[0m \u001b[31m7.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl (207.5 MB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m207.5/207.5 MB\u001b[0m \u001b[31m5.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl (21.1 MB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m21.1/21.1 MB\u001b[0m \u001b[31m82.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading pathos-0.3.3-py3-none-any.whl (82 kB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m82.1/82.1 kB\u001b[0m \u001b[31m7.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading dill-0.3.9-py3-none-any.whl (119 kB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m119.4/119.4 kB\u001b[0m \u001b[31m9.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading multiprocess-0.70.17-py311-none-any.whl (144 kB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m144.3/144.3 kB\u001b[0m \u001b[31m12.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading pox-0.3.5-py3-none-any.whl (29 kB)\n",
            "Downloading ppft-1.7.6.9-py3-none-any.whl (56 kB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m56.8/56.8 kB\u001b[0m \u001b[31m5.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hBuilding wheels for collected packages: pyepo, configspace\n",
            "  Building wheel for pyepo (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
            "  Created wheel for pyepo: filename=pyepo-1.0.0-py3-none-any.whl size=52058 sha256=e3b9cf152be3219905bee318aac02a646ba605ef85370eb80016e7b218db28ce\n",
            "  Stored in directory: /tmp/pip-ephem-wheel-cache-acormoip/wheels/9e/4b/4c/cd1bd6ec37a2decdd12123b131b5da1062b1492f6d2e55bcda\n",
            "  Building wheel for configspace (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n",
            "  Created wheel for configspace: filename=configspace-1.2.1-py3-none-any.whl size=115950 sha256=635e49394cd1b77e7d99b09b2c71f96031fdf8819ed52e19d04ad52e7641700f\n",
            "  Stored in directory: /root/.cache/pip/wheels/11/0f/36/d5027c3eeb038827889830f7efbe6a1bad8956b3eb44ab2f44\n",
            "Successfully built pyepo configspace\n",
            "Installing collected packages: ppft, pox, nvidia-nvjitlink-cu12, nvidia-curand-cu12, nvidia-cufft-cu12, nvidia-cuda-runtime-cu12, nvidia-cuda-nvrtc-cu12, nvidia-cuda-cupti-cu12, nvidia-cublas-cu12, dill, nvidia-cusparse-cu12, nvidia-cudnn-cu12, multiprocess, configspace, pathos, nvidia-cusolver-cu12, pyepo\n",
            "  Attempting uninstall: nvidia-nvjitlink-cu12\n",
            "    Found existing installation: nvidia-nvjitlink-cu12 12.5.82\n",
            "    Uninstalling nvidia-nvjitlink-cu12-12.5.82:\n",
            "      Successfully uninstalled nvidia-nvjitlink-cu12-12.5.82\n",
            "  Attempting uninstall: nvidia-curand-cu12\n",
            "    Found existing installation: nvidia-curand-cu12 10.3.6.82\n",
            "    Uninstalling nvidia-curand-cu12-10.3.6.82:\n",
            "      Successfully uninstalled nvidia-curand-cu12-10.3.6.82\n",
            "  Attempting uninstall: nvidia-cufft-cu12\n",
            "    Found existing installation: nvidia-cufft-cu12 11.2.3.61\n",
            "    Uninstalling nvidia-cufft-cu12-11.2.3.61:\n",
            "      Successfully uninstalled nvidia-cufft-cu12-11.2.3.61\n",
            "  Attempting uninstall: nvidia-cuda-runtime-cu12\n",
            "    Found existing installation: nvidia-cuda-runtime-cu12 12.5.82\n",
            "    Uninstalling nvidia-cuda-runtime-cu12-12.5.82:\n",
            "      Successfully uninstalled nvidia-cuda-runtime-cu12-12.5.82\n",
            "  Attempting uninstall: nvidia-cuda-nvrtc-cu12\n",
            "    Found existing installation: nvidia-cuda-nvrtc-cu12 12.5.82\n",
            "    Uninstalling nvidia-cuda-nvrtc-cu12-12.5.82:\n",
            "      Successfully uninstalled nvidia-cuda-nvrtc-cu12-12.5.82\n",
            "  Attempting uninstall: nvidia-cuda-cupti-cu12\n",
            "    Found existing installation: nvidia-cuda-cupti-cu12 12.5.82\n",
            "    Uninstalling nvidia-cuda-cupti-cu12-12.5.82:\n",
            "      Successfully uninstalled nvidia-cuda-cupti-cu12-12.5.82\n",
            "  Attempting uninstall: nvidia-cublas-cu12\n",
            "    Found existing installation: nvidia-cublas-cu12 12.5.3.2\n",
            "    Uninstalling nvidia-cublas-cu12-12.5.3.2:\n",
            "      Successfully uninstalled nvidia-cublas-cu12-12.5.3.2\n",
            "  Attempting uninstall: nvidia-cusparse-cu12\n",
            "    Found existing installation: nvidia-cusparse-cu12 12.5.1.3\n",
            "    Uninstalling nvidia-cusparse-cu12-12.5.1.3:\n",
            "      Successfully uninstalled nvidia-cusparse-cu12-12.5.1.3\n",
            "  Attempting uninstall: nvidia-cudnn-cu12\n",
            "    Found existing installation: nvidia-cudnn-cu12 9.3.0.75\n",
            "    Uninstalling nvidia-cudnn-cu12-9.3.0.75:\n",
            "      Successfully uninstalled nvidia-cudnn-cu12-9.3.0.75\n",
            "  Attempting uninstall: nvidia-cusolver-cu12\n",
            "    Found existing installation: nvidia-cusolver-cu12 11.6.3.83\n",
            "    Uninstalling nvidia-cusolver-cu12-11.6.3.83:\n",
            "      Successfully uninstalled nvidia-cusolver-cu12-11.6.3.83\n",
            "Successfully installed configspace-1.2.1 dill-0.3.9 multiprocess-0.70.17 nvidia-cublas-cu12-12.4.5.8 nvidia-cuda-cupti-cu12-12.4.127 nvidia-cuda-nvrtc-cu12-12.4.127 nvidia-cuda-runtime-cu12-12.4.127 nvidia-cudnn-cu12-9.1.0.70 nvidia-cufft-cu12-11.2.1.3 nvidia-curand-cu12-10.3.5.147 nvidia-cusolver-cu12-11.6.1.9 nvidia-cusparse-cu12-12.3.1.170 nvidia-nvjitlink-cu12-12.4.127 pathos-0.3.3 pox-0.3.5 ppft-1.7.6.9 pyepo-1.0.0\n"
          ]
        }
      ],
      "source": [
        "# install\n",
        "!git clone -b mpax --depth 1 https://github.com/khalil-research/PyEPO.git\n",
        "!pip install PyEPO/pkg/."
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "!pip install gurobipy\n",
        "!pip install mpax"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "IzFgQG1BhrWU",
        "outputId": "038f1dd2-2ad8-4465-c45a-d9dbca572af1"
      },
      "id": "IzFgQG1BhrWU",
      "execution_count": 2,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Collecting gurobipy\n",
            "  Downloading gurobipy-12.0.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (16 kB)\n",
            "Downloading gurobipy-12.0.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (14.4 MB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m14.4/14.4 MB\u001b[0m \u001b[31m44.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hInstalling collected packages: gurobipy\n",
            "Successfully installed gurobipy-12.0.1\n",
            "Collecting mpax\n",
            "  Downloading mpax-0.1.2-py3-none-any.whl.metadata (8.3 kB)\n",
            "Requirement already satisfied: chex<0.2.0,>=0.1.87 in /usr/local/lib/python3.11/dist-packages (from mpax) (0.1.89)\n",
            "Collecting jax<0.5.0,>=0.4.35 (from mpax)\n",
            "  Downloading jax-0.4.38-py3-none-any.whl.metadata (22 kB)\n",
            "Requirement already satisfied: numpy<2.3,>1.24.4 in /usr/local/lib/python3.11/dist-packages (from mpax) (1.26.4)\n",
            "Requirement already satisfied: absl-py>=0.9.0 in /usr/local/lib/python3.11/dist-packages (from chex<0.2.0,>=0.1.87->mpax) (1.4.0)\n",
            "Requirement already satisfied: typing_extensions>=4.2.0 in /usr/local/lib/python3.11/dist-packages (from chex<0.2.0,>=0.1.87->mpax) (4.12.2)\n",
            "Requirement already satisfied: jaxlib>=0.4.27 in /usr/local/lib/python3.11/dist-packages (from chex<0.2.0,>=0.1.87->mpax) (0.4.33)\n",
            "Requirement already satisfied: toolz>=0.9.0 in /usr/local/lib/python3.11/dist-packages (from chex<0.2.0,>=0.1.87->mpax) (0.12.1)\n",
            "Collecting jaxlib>=0.4.27 (from chex<0.2.0,>=0.1.87->mpax)\n",
            "  Downloading jaxlib-0.4.38-cp311-cp311-manylinux2014_x86_64.whl.metadata (1.0 kB)\n",
            "Requirement already satisfied: ml_dtypes>=0.4.0 in /usr/local/lib/python3.11/dist-packages (from jax<0.5.0,>=0.4.35->mpax) (0.4.1)\n",
            "Requirement already satisfied: opt_einsum in /usr/local/lib/python3.11/dist-packages (from jax<0.5.0,>=0.4.35->mpax) (3.4.0)\n",
            "Requirement already satisfied: scipy>=1.10 in /usr/local/lib/python3.11/dist-packages (from jax<0.5.0,>=0.4.35->mpax) (1.13.1)\n",
            "Downloading mpax-0.1.2-py3-none-any.whl (46 kB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m46.4/46.4 kB\u001b[0m \u001b[31m3.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading jax-0.4.38-py3-none-any.whl (2.2 MB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.2/2.2 MB\u001b[0m \u001b[31m43.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading jaxlib-0.4.38-cp311-cp311-manylinux2014_x86_64.whl (101.8 MB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m101.8/101.8 MB\u001b[0m \u001b[31m7.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hInstalling collected packages: jaxlib, jax, mpax\n",
            "  Attempting uninstall: jaxlib\n",
            "    Found existing installation: jaxlib 0.4.33\n",
            "    Uninstalling jaxlib-0.4.33:\n",
            "      Successfully uninstalled jaxlib-0.4.33\n",
            "  Attempting uninstall: jax\n",
            "    Found existing installation: jax 0.4.33\n",
            "    Uninstalling jax-0.4.33:\n",
            "      Successfully uninstalled jax-0.4.33\n",
            "Successfully installed jax-0.4.38 jaxlib-0.4.38 mpax-0.1.2\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "Remove the problematic hook from Google Colab."
      ],
      "metadata": {
        "id": "Qbeu1M3Y14La"
      },
      "id": "Qbeu1M3Y14La"
    },
    {
      "cell_type": "code",
      "source": [
        "import sys\n",
        "sys.meta_path = [hook for hook in sys.meta_path if not any(keyword in str(hook) for keyword in [\"google.colab\"])]"
      ],
      "metadata": {
        "id": "6eRU14uj14Ww"
      },
      "id": "6eRU14uj14Ww",
      "execution_count": 3,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "id": "7f3af8df",
      "metadata": {
        "id": "7f3af8df"
      },
      "source": [
        "# Training and Testing\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "6a0834bb",
      "metadata": {
        "id": "6a0834bb"
      },
      "outputs": [],
      "source": [
        "# set random seed\n",
        "import random\n",
        "random.seed(42)\n",
        "import numpy as np\n",
        "np.random.seed(42)\n",
        "import torch\n",
        "torch.manual_seed(42)\n",
        "torch.cuda.manual_seed(42)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "65247802",
      "metadata": {
        "id": "65247802"
      },
      "source": [
        "In this tutorial, we will train and test end-to-end predict-then-optimize for different approaches."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cb28d718",
      "metadata": {
        "id": "cb28d718"
      },
      "source": [
        "## 1 Problem Example: Shortest Path"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "753fb690",
      "metadata": {
        "id": "753fb690"
      },
      "source": [
        "Consider a 5x5 grid network again. The figure shows that each node has top, bottom, left, and right neighbors. We aim to find the shortest path from left top to right bottom."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5b439ecf",
      "metadata": {
        "id": "5b439ecf"
      },
      "source": [
        "<img src=\"https://github.com/khalil-research/PyEPO/blob/main/images/shortestpath.png?raw=1\" width=\"500\">"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0e8ecd83",
      "metadata": {
        "id": "0e8ecd83"
      },
      "source": [
        "The weighted graph includes 25 nodes and 40 edges. The weights of the edges are the costs of the path. We load the synthetic dataset where the costs $\\mathbf{c}$ are unknown and can be predicted from features $\\mathbf{x}$.\n",
        "\n",
        "``pyepo.data.shortestpath.genData`` allows us to generate data with various size, polynomial degree, and noise. We can learn more about the dataset [here](https://github.com/khalil-research/PyEPO/blob/main/notebooks/02%20Optimization%20Dataset.ipynb)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "ae4ba391",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:15:30.969643569Z",
          "start_time": "2023-07-03T16:15:30.903584585Z"
        },
        "id": "ae4ba391"
      },
      "outputs": [],
      "source": [
        "import pyepo\n",
        "# generate data\n",
        "grid = (5,5) # grid size\n",
        "num_data = 1000 # number of training data\n",
        "num_feat = 5 # size of feature\n",
        "deg = 4 # polynomial degree\n",
        "e = 0.5 # noise width\n",
        "feats, costs = pyepo.data.shortestpath.genData(num_data+1000, num_feat, grid, deg, e, seed=42)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8210263f",
      "metadata": {
        "id": "8210263f"
      },
      "source": [
        "Thus, we can use ``optGrbModel`` to build a linear model with the Gurobi solver. For more details on ``optModel``, you can read tutorial [01 Optimization Model\n",
        "](https://github.com/khalil-research/PyEPO/blob/main/notebooks/01%20Optimization%20Model.ipynb) and [documentation](https://khalil-research.github.io/PyEPO/build/html/content/examples/model.html)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "29f84eea",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:15:38.795439387Z",
          "start_time": "2023-07-03T16:15:38.731606990Z"
        },
        "id": "29f84eea"
      },
      "outputs": [],
      "source": [
        "# build optModel\n",
        "from pyepo.model.mpax import shortestPathModel\n",
        "optmodel = shortestPathModel(grid=(5,5))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "56225c58",
      "metadata": {
        "id": "56225c58"
      },
      "source": [
        "## 2 Dataset and Data Loader"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ebed5419",
      "metadata": {
        "id": "ebed5419"
      },
      "source": [
        "PyTorch provides two data primitives: ``Dataset`` and ``DataLoader``, which allow you to use pre-loaded datasets as well as your own data. Dataset stores the samples and their corresponding labels, and DataLoader wraps an iterable around the Dataset to enable easy access to the samples. The PyEPO module ``optDataset`` stores features $\\mathbf{x}$ and costs $\\mathbf{c}$, then computes the corresponding optimal solutions $\\mathbf{w}^*$ and objective values $\\mathbf{z}^*$.\n",
        "\n",
        "More details on ``optDataset`` is [here](https://github.com/khalil-research/PyEPO/blob/main/notebooks/02%20Optimization%20Dataset.ipynb)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "9988b779",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:15:42.978633537Z",
          "start_time": "2023-07-03T16:15:42.964467854Z"
        },
        "id": "9988b779"
      },
      "outputs": [],
      "source": [
        "# split train test data\n",
        "from sklearn.model_selection import train_test_split\n",
        "x_train, x_test, c_train, c_test = train_test_split(feats, costs, test_size=1000, random_state=42)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "cfcd58dd",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:15:49.609217956Z",
          "start_time": "2023-07-03T16:15:45.044754480Z"
        },
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "cfcd58dd",
        "outputId": "98707910-90a3-426a-b9fa-76696fab901d"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Restricted license - for non-production use only - expires 2026-11-23\n",
            "Optimizing for optDataset...\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "100%|██████████| 1000/1000 [00:01<00:00, 654.51it/s]\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Optimizing for optDataset...\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "100%|██████████| 1000/1000 [00:00<00:00, 1093.49it/s]\n"
          ]
        }
      ],
      "source": [
        "# get optDataset\n",
        "from pyepo.model.grb import shortestPathModel\n",
        "optmodel_grb = shortestPathModel(grid=(5,5))\n",
        "dataset_train = pyepo.data.dataset.optDataset(optmodel_grb, x_train, c_train)\n",
        "dataset_test = pyepo.data.dataset.optDataset(optmodel_grb, x_test, c_test)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "4fc4bb17",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:15:53.145587193Z",
          "start_time": "2023-07-03T16:15:53.135680577Z"
        },
        "id": "4fc4bb17"
      },
      "outputs": [],
      "source": [
        "# set data loader\n",
        "from torch.utils.data import DataLoader\n",
        "batch_size = 32\n",
        "loader_train = DataLoader(dataset_train, batch_size=batch_size, shuffle=True)\n",
        "loader_test = DataLoader(dataset_test, batch_size=batch_size, shuffle=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7cb4edf9",
      "metadata": {
        "id": "7cb4edf9"
      },
      "source": [
        "## 3 Linear Regression on PyTorch"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f17b53ca",
      "metadata": {
        "id": "f17b53ca"
      },
      "source": [
        "PyTorch is an open-source machine learning library primarily used for developing and training deep learning models such as neural networks. It is developed by Facebook's AI Research lab and is based on the Torch library. PyTorch provides a flexible and intuitive interface for creating and training models.\n",
        "\n",
        "In PyTorch, the ``nn.Module`` is a base class for all neural network modules in the library. It provides a convenient way to organize the layers of a model, and to define the forward pass of the model.\n",
        "\n",
        "Here, we build the simplest PyTorch model, linear regression."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "f7e847e0",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:15:57.092548254Z",
          "start_time": "2023-07-03T16:15:57.070646496Z"
        },
        "id": "f7e847e0"
      },
      "outputs": [],
      "source": [
        "from torch import nn\n",
        "\n",
        "# build linear model\n",
        "class LinearRegression(nn.Module):\n",
        "\n",
        "    def __init__(self):\n",
        "        super(LinearRegression, self).__init__()\n",
        "        self.linear = nn.Linear(num_feat, (grid[0]-1)*grid[1]+(grid[1]-1)*grid[0])\n",
        "\n",
        "    def forward(self, x):\n",
        "        out = self.linear(x)\n",
        "        return out"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1571557e",
      "metadata": {
        "id": "1571557e"
      },
      "source": [
        "Then, we can initialize the predictor."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "3f162584",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:16:02.503758931Z",
          "start_time": "2023-07-03T16:16:02.452869940Z"
        },
        "id": "3f162584"
      },
      "outputs": [],
      "source": [
        "import torch\n",
        "# init model\n",
        "reg = LinearRegression()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "62407a29",
      "metadata": {
        "id": "62407a29"
      },
      "source": [
        "## 4 Training and Testing"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "eebb2ef2",
      "metadata": {
        "id": "eebb2ef2"
      },
      "source": [
        "The core capability of PyEPO is to build optimization models with GurobiPy, Pyomo, or any other solvers and algorithms, then embed the optimization model into an artificial neural network for the end-to-end training. For this purpose, PyEPO implements **SPO+ loss** and **differentiable Black-Box optimizer**, **differentiable perturbed optimizer**, **Fenchel-Young loss with Perturbation**, **Noise Contrastive Estimation**, and **Learning to Rank** as PyTorch autograd modules.\n",
        "\n",
        "We will train and test the above aproaches."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "67152388",
      "metadata": {
        "id": "67152388"
      },
      "source": [
        "### 4.1 Introduction to Metrics"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3731def8",
      "metadata": {
        "id": "3731def8"
      },
      "source": [
        "``pyepo.metric.regret`` is to evaluate model performance. Regret (also called SPO loss) $l_{Regret}(\\hat{\\mathbf{c}}, \\mathbf{c}) = \\mathbf{c}^T \\mathbf{w}^*_{\\hat{\\mathbf{c}}} - z^*_{\\mathbf{c}}$ aims to measure the error in decision-making. It evaluates the distance between the objective value of the solution from predicted cost $\\hat{\\mathbf{c}}$ and the true optimal objective value $z^*_{c}$."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "01a18cb5",
      "metadata": {
        "id": "01a18cb5"
      },
      "source": [
        "To calculate regret, ``pyepo.metric.regret`` requires:\n",
        "- ``predmodel``: a regression neural network for cost prediction\n",
        "- ``optmodel``: an PyEPO optimization model\n",
        "- ``dataloader``: PyTorch dataloader from optDataset to evaluate\n",
        "\n",
        "The following code block is an example:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "d92d811a",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:16:10.429107030Z",
          "start_time": "2023-07-03T16:16:08.916477331Z"
        },
        "id": "d92d811a"
      },
      "outputs": [],
      "source": [
        "import pyepo\n",
        "regret = pyepo.metric.regret(reg, optmodel_grb, loader_test)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dc33c815",
      "metadata": {
        "id": "dc33c815"
      },
      "source": [
        "### 4.2 Functions"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3860d36b",
      "metadata": {
        "id": "3860d36b"
      },
      "source": [
        "Define function to train model with different methods."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "5f4d57e9",
      "metadata": {
        "id": "5f4d57e9"
      },
      "outputs": [],
      "source": [
        "import time\n",
        "\n",
        "# train model\n",
        "def trainModel(reg, loss_func, method_name, num_epochs=20, lr=1e-2):\n",
        "    # set adam optimizer\n",
        "    optimizer = torch.optim.Adam(reg.parameters(), lr=lr)\n",
        "    # train mode\n",
        "    reg.train()\n",
        "    # init log\n",
        "    loss_log = []\n",
        "    loss_log_regret = [pyepo.metric.regret(reg, optmodel, loader_test)]\n",
        "    # init elpased time\n",
        "    elapsed = 0\n",
        "    for epoch in range(num_epochs):\n",
        "        # start timing\n",
        "        tick = time.time()\n",
        "        # load data\n",
        "        for i, data in enumerate(loader_train):\n",
        "            x, c, w, z = data\n",
        "            # cuda\n",
        "            if torch.cuda.is_available():\n",
        "                x, c, w, z = x.cuda(), c.cuda(), w.cuda(), z.cuda()\n",
        "            # forward pass\n",
        "            cp = reg(x)\n",
        "            if method_name == \"spo+\":\n",
        "                loss = loss_func(cp, c, w, z)\n",
        "            if method_name in [\"ptb\", \"pfy\", \"imle\", \"aimle\", \"nce\", \"cmap\"]:\n",
        "                loss = loss_func(cp, w)\n",
        "            if method_name in [\"dbb\", \"nid\"]:\n",
        "                loss = loss_func(cp, c, z)\n",
        "            if method_name in [\"pg\", \"ltr\"]:\n",
        "                loss = loss_func(cp, c)\n",
        "            # backward pass\n",
        "            optimizer.zero_grad()\n",
        "            loss.backward()\n",
        "            optimizer.step()\n",
        "            # record time\n",
        "            tock = time.time()\n",
        "            elapsed += tock - tick\n",
        "            # log\n",
        "            loss_log.append(loss.item())\n",
        "        regret = pyepo.metric.regret(reg, optmodel, loader_test)\n",
        "        loss_log_regret.append(regret)\n",
        "        print(\"Epoch {:2},  Loss: {:9.4f},  Regret: {:7.4f}%\".format(epoch+1, loss.item(), regret*100))\n",
        "    print(\"Total Elapsed Time: {:.2f} Sec.\".format(elapsed))\n",
        "    return loss_log, loss_log_regret"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c5d241e8",
      "metadata": {
        "id": "c5d241e8"
      },
      "source": [
        "Define functions that visualize the learning curves."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "0449b18a",
      "metadata": {
        "id": "0449b18a"
      },
      "outputs": [],
      "source": [
        "from matplotlib import pyplot as plt\n",
        "\n",
        "def visLearningCurve(loss_log, loss_log_regret):\n",
        "    # create figure and subplots\n",
        "    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16,4))\n",
        "\n",
        "    # draw plot for training loss\n",
        "    ax1.plot(loss_log, color=\"c\", lw=2)\n",
        "    ax1.tick_params(axis=\"both\", which=\"major\", labelsize=12)\n",
        "    ax1.set_xlabel(\"Iters\", fontsize=16)\n",
        "    ax1.set_ylabel(\"Loss\", fontsize=16)\n",
        "    ax1.set_title(\"Learning Curve on Training Set\", fontsize=16)\n",
        "\n",
        "    # draw plot for regret on test\n",
        "    ax2.plot(loss_log_regret, color=\"royalblue\", ls=\"--\", alpha=0.7, lw=2)\n",
        "    ax2.set_xticks(range(0, len(loss_log_regret), 2))\n",
        "    ax2.tick_params(axis=\"both\", which=\"major\", labelsize=12)\n",
        "    ax2.set_ylim(0, 0.5)\n",
        "    ax2.set_xlabel(\"Epochs\", fontsize=16)\n",
        "    ax2.set_ylabel(\"Regret\", fontsize=16)\n",
        "    ax2.set_title(\"Learning Curve on Test Set\", fontsize=16)\n",
        "\n",
        "    plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aaacfd38",
      "metadata": {
        "id": "aaacfd38"
      },
      "source": [
        "### 4.3 Surrogate Loss"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dbaf544a-f3f3-4c2f-a6d6-67ed0de2135b",
      "metadata": {
        "id": "dbaf544a-f3f3-4c2f-a6d6-67ed0de2135b"
      },
      "source": [
        "#### 4.3.1 Smart Predict-then-Optimize"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cba77671",
      "metadata": {
        "id": "cba77671"
      },
      "source": [
        "[Elmachtoub, A. N., & Grigas, P. (2022). Smart “predict, then optimize”. Management Science, 68(1), 9-26.](https://doi.org/10.1287/mnsc.2020.3922)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "905f8b07",
      "metadata": {
        "id": "905f8b07"
      },
      "source": [
        "SPO+ Loss is a surrogate loss function of SPO Loss (Regret), which measures the decision error of optimization problem. For SPO/SPO+ Loss, the objective function is linear and constraints are known and fixed, but the cost vector need to be predicted from contextual data. The SPO+ Loss is convex with non-zero subgradient. Thus, allows us to design an algorithm based on stochastic gradient descent."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0c03249b",
      "metadata": {
        "id": "0c03249b"
      },
      "source": [
        "First of all, we initialize the predictor."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "1586bcfe",
      "metadata": {
        "id": "1586bcfe"
      },
      "outputs": [],
      "source": [
        "import torch\n",
        "# init model\n",
        "reg = LinearRegression()\n",
        "# cuda\n",
        "if torch.cuda.is_available():\n",
        "    reg = reg.cuda()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "23d3f88e",
      "metadata": {
        "id": "23d3f88e"
      },
      "source": [
        "``pyepo.func.SPOPlus`` allows us to set a SPO+ loss for training, which requires parameters:\n",
        "- ``optmodel``: an PyEPO optimization model\n",
        "- ``processes``: number of processors for multi-thread, 1 for single-core, 0 for all of cores"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "94f93c65",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "94f93c65",
        "outputId": "21430387-65aa-4b82-b314-6dc1976de0bd"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Num of cores: 1\n"
          ]
        }
      ],
      "source": [
        "# init SPO+ loss\n",
        "spop = pyepo.func.SPOPlus(optmodel)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a6e32619",
      "metadata": {
        "id": "a6e32619"
      },
      "source": [
        "##### Training"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "89fe3311",
      "metadata": {
        "id": "89fe3311"
      },
      "source": [
        "Then, we can train a linear predictor with SPO+ loss to predict unknown cost coefficients and make decisions.\n",
        "\n",
        "To compute SPO+ loss, ``spop`` requires $\\mathbf{\\hat{c}}$, $\\mathbf{c}$, $\\mathbf{w}^*_{\\mathbf{c}}$, and $\\mathbf{z}^*_{\\mathbf{c}}$."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "25c259af",
      "metadata": {
        "id": "25c259af"
      },
      "outputs": [],
      "source": [
        "loss_log, loss_log_regret = trainModel(reg, loss_func=spop, method_name=\"spo+\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4ed7c5ab",
      "metadata": {
        "id": "4ed7c5ab"
      },
      "source": [
        "##### Learning Curve"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "dde8491a",
      "metadata": {
        "id": "dde8491a"
      },
      "outputs": [],
      "source": [
        "visLearningCurve(loss_log, loss_log_regret)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4bf71345-4a82-479d-a2b6-31ebe58f1eda",
      "metadata": {
        "id": "4bf71345-4a82-479d-a2b6-31ebe58f1eda"
      },
      "source": [
        "#### 4.3.2 Perturbation Gradient"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "41779b0f-a9b0-408b-af26-a7ede7ab1167",
      "metadata": {
        "id": "41779b0f-a9b0-408b-af26-a7ede7ab1167"
      },
      "source": [
        "[Decision-Focused Learning with Directional Gradients\n",
        "](https://arxiv.org/abs/2402.03256)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "231d9668-fba1-4657-82b8-392a4c7311f9",
      "metadata": {
        "id": "231d9668-fba1-4657-82b8-392a4c7311f9"
      },
      "source": [
        "PG Loss is a surrogate loss function of objective value, which measures the decision quality of the optimization problem. For PG Loss, the objective function is linear, and constraints are known and fixed, but the cost vector needs to be predicted from contextual data.\n",
        "\n",
        "According to Danskin’s Theorem, the PG Loss is derived from different zeroth order approximations and has the informative gradient. Thus, it allows us to design an algorithm based on stochastic gradient descent."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "017d3208-75fd-4b5e-ad36-6b0a83df0987",
      "metadata": {
        "id": "017d3208-75fd-4b5e-ad36-6b0a83df0987"
      },
      "outputs": [],
      "source": [
        "import torch\n",
        "# init model\n",
        "reg = LinearRegression()\n",
        "# cuda\n",
        "if torch.cuda.is_available():\n",
        "    reg = reg.cuda()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "30438999-b78d-44e7-bb08-ab42897fa025",
      "metadata": {
        "id": "30438999-b78d-44e7-bb08-ab42897fa025"
      },
      "source": [
        "##### Training"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e5a46aab-0e9a-45bb-ae9c-8f77663a4b73",
      "metadata": {
        "id": "e5a46aab-0e9a-45bb-ae9c-8f77663a4b73"
      },
      "source": [
        "Then, we can train a linear predictor with PG loss to predict unknown cost coefficients and make decisions.\n",
        "\n",
        "To compute PG loss, ``pg`` requires $\\mathbf{\\hat{c}}$, $\\mathbf{c}$."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "0a074627-7dfa-4a18-aed9-0cbfbbae956b",
      "metadata": {
        "id": "0a074627-7dfa-4a18-aed9-0cbfbbae956b"
      },
      "outputs": [],
      "source": [
        "# init PG loss\n",
        "pg = pyepo.func.perturbationGradient(optmodel, sigma=0.1, two_sides=False)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "4f1fcc67-3c5c-4605-9b8f-c419a1f1bb56",
      "metadata": {
        "id": "4f1fcc67-3c5c-4605-9b8f-c419a1f1bb56"
      },
      "outputs": [],
      "source": [
        "loss_log, loss_log_regret = trainModel(reg, loss_func=pg, method_name=\"pg\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5a540bbd-4ef6-4e85-a7e8-c5c79255ce26",
      "metadata": {
        "id": "5a540bbd-4ef6-4e85-a7e8-c5c79255ce26"
      },
      "source": [
        "##### Learning Curve"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c094cf15-c218-4080-887f-79b7450b4f47",
      "metadata": {
        "id": "c094cf15-c218-4080-887f-79b7450b4f47"
      },
      "outputs": [],
      "source": [
        "visLearningCurve(loss_log, loss_log_regret)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3dee3614",
      "metadata": {
        "id": "3dee3614"
      },
      "source": [
        "### 4.4 Perturbed Methods"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "89a1a70b",
      "metadata": {
        "id": "89a1a70b"
      },
      "source": [
        "[Berthet, Q., Blondel, M., Teboul, O., Cuturi, M., Vert, J. P., & Bach, F. (2020). Learning with differentiable pertubed optimizers. Advances in neural information processing systems, 33, 9508-9519.](https://papers.nips.cc/paper/2020/hash/6bb56208f672af0dd65451f869fedfd9-Abstract.html)\n",
        "\n",
        "[Niepert, M., Minervini, P., & Franceschi, L. (2021). Implicit MLE: backpropagating through discrete exponential family distributions. Advances in Neural Information Processing Systems, 34, 14567-14579.](https://proceedings.neurips.cc/paper_files/paper/2021/hash/7a430339c10c642c4b2251756fd1b484-Abstract.html)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2d230790",
      "metadata": {
        "id": "2d230790"
      },
      "source": [
        "#### 4.4.1 Differentiable Perturbed Optimizer"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1dc891ad",
      "metadata": {
        "id": "1dc891ad"
      },
      "source": [
        "Differentiable perturbed optimizer uses Monte-Carlo samples to estimate solutions, in which randomly perturbed costs are sampled to optimize. For the perturbed optimizer, the cost vector needs to be predicted from contextual data and are perturbed with Gaussian noise. The perturbed optimizer is differentiable in its inputs with non-zero Jacobian, thus allowing us to design an algorithm based on stochastic gradient descent."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e5f505f2",
      "metadata": {
        "id": "e5f505f2"
      },
      "source": [
        "First of all, we initialize the predictor."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "97df2499",
      "metadata": {
        "id": "97df2499"
      },
      "outputs": [],
      "source": [
        "import torch\n",
        "# init model\n",
        "reg = LinearRegression()\n",
        "# cuda\n",
        "if torch.cuda.is_available():\n",
        "    reg = reg.cuda()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bf545748",
      "metadata": {
        "id": "bf545748"
      },
      "source": [
        "``pyepo.func.perturbedOpt`` allows us to set a SPO+ loss for training, which requires parameters:\n",
        "- ``optmodel``: a PyEPO optimization model\n",
        "- ``n_samples``: number of Monte-Carlo samples\n",
        "- ``sigma``:  the amplitude of the perturbation for costs\n",
        "- ``processes``: number of processors for multi-thread, 1 for single-core, 0 for all of the cores\n",
        "- ``seed``: random state seed for perturbations"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2993c33a",
      "metadata": {
        "id": "2993c33a"
      },
      "source": [
        "Unlike SPO+, PTB is not a loss function so we need to define a loss."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "036ff330",
      "metadata": {
        "id": "036ff330"
      },
      "outputs": [],
      "source": [
        "# init ptb solver\n",
        "ptb = pyepo.func.perturbedOpt(optmodel, n_samples=3, sigma=1.0)\n",
        "# set loss\n",
        "l1 = nn.L1Loss()\n",
        "\n",
        "# loss function\n",
        "def ptbl1(cp, w):\n",
        "    # perturbed optimizer\n",
        "    we = ptb(cp)\n",
        "    # loss\n",
        "    loss = l1(we, w)\n",
        "    return loss"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e5adad0e",
      "metadata": {
        "id": "e5adad0e"
      },
      "source": [
        "##### Training"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "303620e3",
      "metadata": {
        "id": "303620e3"
      },
      "outputs": [],
      "source": [
        "loss_log, loss_log_regret = trainModel(reg, loss_func=ptbl1, method_name=\"ptb\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c0bb34fe",
      "metadata": {
        "id": "c0bb34fe"
      },
      "source": [
        "##### Learning Curve"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "f4223d18",
      "metadata": {
        "id": "f4223d18"
      },
      "outputs": [],
      "source": [
        "visLearningCurve(loss_log, loss_log_regret)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e0b7d864",
      "metadata": {
        "id": "e0b7d864"
      },
      "source": [
        "#### 4.4.2 Perturbed Fenchel-Young Loss"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3d918b9e",
      "metadata": {
        "id": "3d918b9e"
      },
      "source": [
        "Perturbed Fenchel-Young loss (PYFL) uses perturbation techniques with Monte-Carlo samples. The use of the loss improves the algorithmic by the specific expression of the gradients of the loss. For the perturbed optimizer, the cost vector need to be predicted from contextual data and are perturbed with Gaussian noise. The Fenchel-Young loss allows to directly optimize a loss between the features and solutions with less computation. Thus, allows us to design an algorithm based on stochastic gradient descent."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5b20cf56",
      "metadata": {
        "id": "5b20cf56"
      },
      "source": [
        "First of all, we initialize the predictor."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "8353c159",
      "metadata": {
        "id": "8353c159"
      },
      "outputs": [],
      "source": [
        "import torch\n",
        "# init model\n",
        "reg = LinearRegression()\n",
        "# cuda\n",
        "if torch.cuda.is_available():\n",
        "    reg = reg.cuda()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "89ad1b86",
      "metadata": {
        "id": "89ad1b86"
      },
      "source": [
        "``pyepo.func.perturbedFenchelYoung`` allows us to set a Fenchel-Young loss for training, which requires parameters:\n",
        "- ``optmodel``: a PyEPO optimization model\n",
        "- ``n_samples``: number of Monte-Carlo samples\n",
        "- ``sigma``:  the amplitude of the perturbation for costs\n",
        "- ``processes``: number of processors for multi-thread, 1 for single-core, 0 for all of the cores\n",
        "- ``seed``: random state seed for perturbations"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "8e1ba873",
      "metadata": {
        "id": "8e1ba873"
      },
      "outputs": [],
      "source": [
        "# init pfyl loss\n",
        "pfy = pyepo.func.perturbedFenchelYoung(optmodel, n_samples=3, sigma=1.0)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "95ababe4",
      "metadata": {
        "id": "95ababe4"
      },
      "source": [
        "##### Training"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b6deb950",
      "metadata": {
        "id": "b6deb950"
      },
      "source": [
        "Then, we can train a linear predictor with Fenchel-Young loss to predict unknown cost coefficients and make decisions.\n",
        "\n",
        "To compute Fenchel-Young loss, ``pfy`` requires $\\mathbf{\\hat{c}}$ and $\\mathbf{w}^*_{\\mathbf{c}}$."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "cc50c3d8",
      "metadata": {
        "id": "cc50c3d8"
      },
      "outputs": [],
      "source": [
        "loss_log, loss_log_regret = trainModel(reg, loss_func=pfy, method_name=\"pfy\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e663292c",
      "metadata": {
        "id": "e663292c"
      },
      "source": [
        "##### Learning Curve"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "0fd40036",
      "metadata": {
        "id": "0fd40036"
      },
      "outputs": [],
      "source": [
        "visLearningCurve(loss_log, loss_log_regret)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "IK-MVo0s5xET",
      "metadata": {
        "id": "IK-MVo0s5xET"
      },
      "source": [
        "#### 4.4.3 Implicit Maximum Likelihood Estimator"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "tRJDHUEH5zoN",
      "metadata": {
        "id": "tRJDHUEH5zoN"
      },
      "source": [
        "Implicit Maximum Likelihood Estimator (I-MLE) use the perturb-and-MAP framework. They sample noise from a Sum-of-Gamma distribution and interpolate the loss function to approximate finite difference."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "yWi85vkE5_EE",
      "metadata": {
        "id": "yWi85vkE5_EE"
      },
      "source": [
        "First of all, we initialize the predictor."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "FDhj882n5z0s",
      "metadata": {
        "id": "FDhj882n5z0s"
      },
      "outputs": [],
      "source": [
        "import torch\n",
        "# init model\n",
        "reg = LinearRegression()\n",
        "# cuda\n",
        "if torch.cuda.is_available():\n",
        "    reg = reg.cuda()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "drLpLVGO6gP_",
      "metadata": {
        "id": "drLpLVGO6gP_"
      },
      "source": [
        "``pyepo.func.implicitMLE`` allows us to set a Maximum Likelihood loss for training, which requires parameters:\n",
        "- ``optmodel``: a PyEPO optimization model\n",
        "- ``n_samples``: number of Monte-Carlo samples\n",
        "- ``sigma``:  the amplitude of the perturbation for costs\n",
        "- ``lambd``: a hyperparameter for differentiable block-box to control interpolation degree\n",
        "- ``distribution``: noise distribution\n",
        "- ``processes``: number of processors for multi-thread, 1 for single-core, 0 for all of the cores\n",
        "- ``seed``: random state seed for perturbations"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "YiRPpUYx6Dtl",
      "metadata": {
        "id": "YiRPpUYx6Dtl"
      },
      "outputs": [],
      "source": [
        "# init i-mle solver\n",
        "imle = pyepo.func.implicitMLE(optmodel, n_samples=10, sigma=1.0, lambd=10)\n",
        "# set loss\n",
        "l1 = nn.L1Loss()\n",
        "\n",
        "# loss function\n",
        "def ilmel1(cp, w):\n",
        "    # perturbed optimizer\n",
        "    we = imle(cp)\n",
        "    # loss\n",
        "    loss = l1(we, w)\n",
        "    return loss"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c6SV0HSi7mKJ",
      "metadata": {
        "id": "c6SV0HSi7mKJ"
      },
      "source": [
        "##### Training"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "-M2cSiLF7odY",
      "metadata": {
        "id": "-M2cSiLF7odY"
      },
      "outputs": [],
      "source": [
        "loss_log, loss_log_regret = trainModel(reg, loss_func=ilmel1, method_name=\"imle\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "MT6Obx6176Qn",
      "metadata": {
        "id": "MT6Obx6176Qn"
      },
      "source": [
        "##### Learning Curve"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "Uhc4ZUO37r8v",
      "metadata": {
        "id": "Uhc4ZUO37r8v"
      },
      "outputs": [],
      "source": [
        "visLearningCurve(loss_log, loss_log_regret)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "E4b9DOrQZzpL",
      "metadata": {
        "id": "E4b9DOrQZzpL"
      },
      "source": [
        "4.4.4 Adaptive Implicit Maximum Likelihood Estimator"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "RMN_FjetaEQk",
      "metadata": {
        "id": "RMN_FjetaEQk"
      },
      "source": [
        "Adaptive Implicit Maximum Likelihood Estimator (AI-MLE) use the adaptive interpolation step and the perturb-and-MAP framework. They sample noise from a Sum-of-Gamma distribution and interpolate the loss function to approximate finite difference."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "k89xeam7aIn0",
      "metadata": {
        "id": "k89xeam7aIn0"
      },
      "source": [
        "First of all, we initialize the predictor."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "caArAOgjZ9-z",
      "metadata": {
        "id": "caArAOgjZ9-z"
      },
      "outputs": [],
      "source": [
        "import torch\n",
        "# init model\n",
        "reg = LinearRegression()\n",
        "# cuda\n",
        "if torch.cuda.is_available():\n",
        "    reg = reg.cuda()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "Nkri9IMWaMYU",
      "metadata": {
        "id": "Nkri9IMWaMYU"
      },
      "source": [
        "``pyepo.func.implicitMLE`` allows us to set a Maximum Likelihood loss for training, which requires parameters:\n",
        "- ``optmodel``: a PyEPO optimization model\n",
        "- ``n_samples``: number of Monte-Carlo samples\n",
        "- ``sigma``:  the amplitude of the perturbation for costs\n",
        "- ``distribution``: noise distribution\n",
        "- ``processes``: number of processors for multi-thread, 1 for single-core, 0 for all of the cores\n",
        "- ``seed``: random state seed for perturbations"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6M6AR36XZ-HD",
      "metadata": {
        "id": "6M6AR36XZ-HD"
      },
      "outputs": [],
      "source": [
        "# init i-mle solver\n",
        "aimle = pyepo.func.adaptiveImplicitMLE(optmodel, n_samples=10, sigma=1.0)\n",
        "\n",
        "# set loss\n",
        "l1 = nn.L1Loss()\n",
        "\n",
        "# loss function\n",
        "def ilmel1(cp, w):\n",
        "    # perturbed optimizer\n",
        "    we = aimle(cp)\n",
        "    # loss\n",
        "    loss = l1(we, w)\n",
        "    return loss"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "AIOwW4VVa66b",
      "metadata": {
        "id": "AIOwW4VVa66b"
      },
      "source": [
        "##### Training"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "CqiPkxGYZ-N2",
      "metadata": {
        "id": "CqiPkxGYZ-N2"
      },
      "outputs": [],
      "source": [
        "loss_log, loss_log_regret = trainModel(reg, loss_func=ilmel1, method_name=\"aimle\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "VbQmHccna28i",
      "metadata": {
        "id": "VbQmHccna28i"
      },
      "source": [
        "##### Learning Curve"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "0oIXPmaDa3KR",
      "metadata": {
        "id": "0oIXPmaDa3KR"
      },
      "outputs": [],
      "source": [
        "visLearningCurve(loss_log, loss_log_regret)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3098e8bc",
      "metadata": {
        "id": "3098e8bc"
      },
      "source": [
        "### 4.5 Black-Box Methods"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "qWtP5Ih4doL0",
      "metadata": {
        "id": "qWtP5Ih4doL0"
      },
      "source": [
        "#### 4.5.1 Differentiable Black-Box Optimizer"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e36b38d3",
      "metadata": {
        "id": "e36b38d3"
      },
      "source": [
        "[Pogančić, M. V., Paulus, A., Musil, V., Martius, G., & Rolinek, M. (2020). Differentiation of blackbox combinatorial solvers. In International Conference on Learning Representations.](https://arxiv.org/abs/1912.02175)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e4360852",
      "metadata": {
        "id": "e4360852"
      },
      "source": [
        "Differentiable black-box optimizer estimates gradients from interpolation, replacing the zero gradients. For differentiable block-box, the objective function is linear and constraints are known and fixed, but the cost vector need to be predicted from contextual data."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "012b0fe5",
      "metadata": {
        "id": "012b0fe5"
      },
      "source": [
        "First of all, we initialize the predictor"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "96ce18e8",
      "metadata": {
        "id": "96ce18e8"
      },
      "outputs": [],
      "source": [
        "import torch\n",
        "# init model\n",
        "reg = LinearRegression()\n",
        "# cuda\n",
        "if torch.cuda.is_available():\n",
        "    reg = reg.cuda()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6e91a091",
      "metadata": {
        "id": "6e91a091"
      },
      "source": [
        "``pyepo.func.blackboxOpt`` allows us to set a differentiable solver, which requires parameters:\n",
        "- ``optmodel``: a PyEPO optimization model\n",
        "- ``lambd``: a parameter to contral interpolation degree\n",
        "- ``processes``: number of processors for multi-thread, 1 for single-core, 0 for all of the cores"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "8cbb1806",
      "metadata": {
        "id": "8cbb1806"
      },
      "outputs": [],
      "source": [
        "# init dbb solver\n",
        "dbb = pyepo.func.blackboxOpt(optmodel, lambd=20)\n",
        "# set loss\n",
        "l1 = nn.L1Loss()\n",
        "\n",
        "# loss function\n",
        "def dbbl1(cp, c, z):\n",
        "    # black-box optimizer\n",
        "    wp = dbb(cp)\n",
        "    # objective value\n",
        "    zp = (wp * c).sum(1).view(-1, 1)\n",
        "    # loss\n",
        "    loss = l1(zp, z)\n",
        "    return loss"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c4053880",
      "metadata": {
        "id": "c4053880"
      },
      "source": [
        "##### Training"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6acc5d3f",
      "metadata": {
        "id": "6acc5d3f"
      },
      "outputs": [],
      "source": [
        "loss_log, loss_log_regret = trainModel(reg, loss_func=dbbl1, method_name=\"dbb\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "63fe2239",
      "metadata": {
        "id": "63fe2239"
      },
      "source": [
        "##### Learning Curve"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "ae895239",
      "metadata": {
        "id": "ae895239"
      },
      "outputs": [],
      "source": [
        "visLearningCurve(loss_log, loss_log_regret)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "N1BOfVSxd1Pl",
      "metadata": {
        "id": "N1BOfVSxd1Pl"
      },
      "source": [
        "#### 4.5.2 Negative Identity Backpropagation (NID)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "15lg0xVrd1Ps",
      "metadata": {
        "id": "15lg0xVrd1Ps"
      },
      "source": [
        "[Sahoo, S. S., Paulus, A., Vlastelica, M., Musil, V., Kuleshov, V., & Martius, G. (2022). Backpropagation through combinatorial algorithms: Identity with projection works. arXiv preprint arXiv:2205.15213.](https://arxiv.org/abs/2205.15213)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "qZ8jtTHkd1Ps",
      "metadata": {
        "id": "qZ8jtTHkd1Ps"
      },
      "source": [
        "Negative Identity Backpropagation (NID) treats the solver as a negative identity mapping during the backward pass, which is equivalent to DBB with certain hyperparameter. It is hyperparameter-free and does not require any additional computationally expensive call to the solver on the backward pass."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3YfO0smjd1Ps",
      "metadata": {
        "id": "3YfO0smjd1Ps"
      },
      "source": [
        "First of all, we initialize the predictor"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "kP9IVVpUd1Ps",
      "metadata": {
        "id": "kP9IVVpUd1Ps"
      },
      "outputs": [],
      "source": [
        "import torch\n",
        "# init model\n",
        "reg = LinearRegression()\n",
        "# cuda\n",
        "if torch.cuda.is_available():\n",
        "    reg = reg.cuda()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "kkNDsLbmd1Ps",
      "metadata": {
        "id": "kkNDsLbmd1Ps"
      },
      "source": [
        "``pyepo.func.blackboxOpt`` allows us to set a differentiable solver, which requires parameters:\n",
        "- ``optmodel``: a PyEPO optimization model\n",
        "- ``processes``: number of processors for multi-thread, 1 for single-core, 0 for all of the cores"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "MdJz4Oi2d1Ps",
      "metadata": {
        "id": "MdJz4Oi2d1Ps"
      },
      "outputs": [],
      "source": [
        "# init dbb solver\n",
        "nid = pyepo.func.negativeIdentity(optmodel)\n",
        "# set loss\n",
        "l1 = nn.L1Loss()\n",
        "\n",
        "# loss function\n",
        "def nidl1(cp, c, z):\n",
        "    # black-box optimizer\n",
        "    wp = nid(cp)\n",
        "    # objective value\n",
        "    zp = (wp * c).sum(1).view(-1, 1)\n",
        "    # loss\n",
        "    loss = l1(zp, z)\n",
        "    return loss"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "FZqOwcRsd1Pt",
      "metadata": {
        "id": "FZqOwcRsd1Pt"
      },
      "source": [
        "##### Training"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "rXKg0nkVd1Pt",
      "metadata": {
        "id": "rXKg0nkVd1Pt"
      },
      "outputs": [],
      "source": [
        "loss_log, loss_log_regret = trainModel(reg, loss_func=nidl1, method_name=\"nid\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "xtIcL5EXd1Pt",
      "metadata": {
        "id": "xtIcL5EXd1Pt"
      },
      "source": [
        "##### Learning Curve"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "MbHqlmbVd1Pt",
      "metadata": {
        "id": "MbHqlmbVd1Pt"
      },
      "outputs": [],
      "source": [
        "visLearningCurve(loss_log, loss_log_regret)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0e74c152",
      "metadata": {
        "id": "0e74c152"
      },
      "source": [
        "### 4.6 Contrastive Estimation"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8b9a1d00",
      "metadata": {
        "id": "8b9a1d00"
      },
      "source": [
        "[Mulamba, M., Mandi, J., Diligenti, M., Lombardi, M., Bucarey, V., & Guns, T. (2021). Contrastive losses and solution caching for predict-and-optimize. Proceedings of the Thirtieth International Joint Conference on Artificial Intelligence.](https://www.ijcai.org/proceedings/2021/390)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7fdca6a1",
      "metadata": {
        "id": "7fdca6a1"
      },
      "source": [
        "#### 4.6.1 Noise Contrastive Estimation"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7eaab10b",
      "metadata": {
        "id": "7eaab10b"
      },
      "source": [
        "It uses a noise contrastive approach to motivate a family of surrogate loss functions, based on viewing non-optimal solutions as negative examples. For the NCE, the cost vector needs to be predicted from contextual data and maximizes the separation of the probability of the optimal solution."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5b407567",
      "metadata": {
        "id": "5b407567"
      },
      "source": [
        "First of all, we initialize the predictor"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "b447a5f7",
      "metadata": {
        "id": "b447a5f7"
      },
      "outputs": [],
      "source": [
        "import torch\n",
        "# init model\n",
        "reg = LinearRegression()\n",
        "# cuda\n",
        "if torch.cuda.is_available():\n",
        "    reg = reg.cuda()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "841c0a44",
      "metadata": {
        "id": "841c0a44"
      },
      "source": [
        "``pyepo.func.NCE`` allows us to use a noise contrastive estimiation loss for training, which requires parameters:\n",
        "- ``optmodel``: an PyEPO optimization model\n",
        "- ``processes``: number of processors for multi-thread, 1 for single-core, 0 for all of cores\n",
        "- ``solve_ratio``: the ratio of new solutions computed during training\n",
        "- ``dataset``: a dataset to initialize the solution pool with. Usually this is simply the training set"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "19fc8c2b",
      "metadata": {
        "id": "19fc8c2b"
      },
      "outputs": [],
      "source": [
        "nce = pyepo.func.NCE(optmodel, solve_ratio=0.05, dataset=dataset_train)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0b831428",
      "metadata": {
        "id": "0b831428"
      },
      "source": [
        "##### Training"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4e15a116",
      "metadata": {
        "id": "4e15a116"
      },
      "source": [
        "To compute noise contrastive estimation loss, ``nce`` requires $\\mathbf{\\hat{c}}$ and $\\mathbf{w}^*_{\\mathbf{c}}$."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "b863fea1",
      "metadata": {
        "id": "b863fea1"
      },
      "outputs": [],
      "source": [
        "loss_log, loss_log_regret = trainModel(reg, loss_func=nce, method_name=\"nce\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8f879771",
      "metadata": {
        "id": "8f879771"
      },
      "source": [
        "##### Learning Curve"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "8c8ad61e",
      "metadata": {
        "id": "8c8ad61e"
      },
      "outputs": [],
      "source": [
        "visLearningCurve(loss_log, loss_log_regret)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "75e35693",
      "metadata": {
        "id": "75e35693"
      },
      "source": [
        "#### 4.6.2 Maximum A Posterior"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4aa00bb7",
      "metadata": {
        "id": "4aa00bb7"
      },
      "source": [
        "An autograd module for Maximum A Posterior contrastive estimation as surrogate loss functions, which is a efficient self-contrastive algorithm. For the MAP, the cost vector needs to be predicted from contextual data and maximizes the separation of the probability of the optimal solution."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b09586e6",
      "metadata": {
        "id": "b09586e6"
      },
      "source": [
        "First of all, we initialize the predictor"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "52152357",
      "metadata": {
        "id": "52152357"
      },
      "outputs": [],
      "source": [
        "import torch\n",
        "# init model\n",
        "reg = LinearRegression()\n",
        "# cuda\n",
        "if torch.cuda.is_available():\n",
        "    reg = reg.cuda()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8319d359",
      "metadata": {
        "id": "8319d359"
      },
      "source": [
        "``pyepo.func.contrastiveMAP`` allows us to use a noise contrastive estimiation loss for training, which requires parameters:\n",
        "- ``optmodel``: an PyEPO optimization model\n",
        "- ``processes``: number of processors for multi-thread, 1 for single-core, 0 for all of cores\n",
        "- ``solve_ratio``: the ratio of new solutions computed during training\n",
        "- ``dataset``: a dataset to initialize the solution pool with. Usually this is simply the training set"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "0b960707",
      "metadata": {
        "id": "0b960707"
      },
      "outputs": [],
      "source": [
        "cmap = pyepo.func.contrastiveMAP(optmodel, solve_ratio=0.05, dataset=dataset_train)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b060aa52",
      "metadata": {
        "id": "b060aa52"
      },
      "source": [
        "##### Training"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6929f837",
      "metadata": {
        "id": "6929f837"
      },
      "source": [
        "To compute Maximum A Posterior contrastive estimation loss, ``cmap`` requires $\\mathbf{\\hat{c}}$ and $\\mathbf{w}^*_{\\mathbf{c}}$."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "3ef81b1a",
      "metadata": {
        "id": "3ef81b1a"
      },
      "outputs": [],
      "source": [
        "loss_log, loss_log_regret = trainModel(reg, loss_func=cmap, method_name=\"cmap\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "108fd0a4",
      "metadata": {
        "id": "108fd0a4"
      },
      "source": [
        "##### Learning Curve"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "4196c11a",
      "metadata": {
        "id": "4196c11a"
      },
      "outputs": [],
      "source": [
        "visLearningCurve(loss_log, loss_log_regret)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6abb4845",
      "metadata": {
        "id": "6abb4845"
      },
      "source": [
        "### 4.7 Learning To Rank"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5cd0621d",
      "metadata": {
        "id": "5cd0621d"
      },
      "source": [
        "[Mandi, J., Bucarey, V., Mulamba, M., & Guns, T. (2022). Decision-focused learning: through the lens of learning to rank. Proceedings of the 39th International Conference on Machine Learning.](https://proceedings.mlr.press/v162/mandi22a.html)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6bbc75cb",
      "metadata": {
        "id": "6bbc75cb"
      },
      "source": [
        "#### 4.7.1 Listwise Learning To Rank"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2327a93b",
      "metadata": {
        "id": "2327a93b"
      },
      "source": [
        "A autograd module for listwise learning to rank, where the goal is to learn an objective function that ranks a pool of feasible solutions correctly. For the listwise LTR, the cost vector needs to be predicted from contextual data, and the loss measures the scores of the whole ranked lists."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4c8046eb",
      "metadata": {
        "id": "4c8046eb"
      },
      "source": [
        "First of all, we initialize the predictor"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "602505a8",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:16:24.267717164Z",
          "start_time": "2023-07-03T16:16:18.452617457Z"
        },
        "id": "602505a8"
      },
      "outputs": [],
      "source": [
        "import torch\n",
        "# init model\n",
        "reg = LinearRegression()\n",
        "# cuda\n",
        "if torch.cuda.is_available():\n",
        "    reg = reg.cuda()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "89c56904",
      "metadata": {
        "id": "89c56904"
      },
      "source": [
        "``pyepo.func.listwiseLTR`` allows us to use a listwise learning to rank loss for training, which requires parameters:\n",
        "- ``optmodel``: an PyEPO optimization model\n",
        "- ``processes``: number of processors for multi-thread, 1 for single-core, 0 for all of cores\n",
        "- ``solve_ratio``: the ratio of new solutions computed during training\n",
        "- ``dataset``: a dataset to initialize the solution pool with. Usually this is simply the training set"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6b33d585",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:16:27.478976953Z",
          "start_time": "2023-07-03T16:16:27.473935240Z"
        },
        "id": "6b33d585"
      },
      "outputs": [],
      "source": [
        "lsltr = pyepo.func.listwiseLTR(optmodel, solve_ratio=0.05, dataset=dataset_train)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e8d837cc",
      "metadata": {
        "id": "e8d837cc"
      },
      "source": [
        "##### Training"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "32cb0d1d",
      "metadata": {
        "id": "32cb0d1d"
      },
      "source": [
        "To compute learning-to-rank loss, ``lsltr`` requires $\\mathbf{\\hat{c}}$ and $\\mathbf{c}$."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "fa65886a",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:17:01.763318176Z",
          "start_time": "2023-07-03T16:16:32.516958317Z"
        },
        "id": "fa65886a"
      },
      "outputs": [],
      "source": [
        "loss_log, loss_log_regret = trainModel(reg, loss_func=lsltr, method_name=\"ltr\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3f0dd4c7",
      "metadata": {
        "id": "3f0dd4c7"
      },
      "source": [
        "##### Learning Curve"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "23f5ec24",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:17:46.308808781Z",
          "start_time": "2023-07-03T16:17:45.655774057Z"
        },
        "id": "23f5ec24"
      },
      "outputs": [],
      "source": [
        "visLearningCurve(loss_log, loss_log_regret)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bf527d34",
      "metadata": {
        "id": "bf527d34"
      },
      "source": [
        "#### 4.7.2 Pairwise Learning To Rank"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7477432c",
      "metadata": {
        "id": "7477432c"
      },
      "source": [
        "An autograd module for pairwise learning to rank, where the goal is to learn an objective function that ranks a pool of feasible solutions correctly. For the pairwise LTR, the cost vector needs to be predicted from contextual data, and the loss learns the relative ordering of pairs of items."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ea1cde2b",
      "metadata": {
        "id": "ea1cde2b"
      },
      "source": [
        "First of all, we initialize the predictor"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "af18ca38",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:16:24.267717164Z",
          "start_time": "2023-07-03T16:16:18.452617457Z"
        },
        "id": "af18ca38"
      },
      "outputs": [],
      "source": [
        "import torch\n",
        "# init model\n",
        "reg = LinearRegression()\n",
        "# cuda\n",
        "if torch.cuda.is_available():\n",
        "    reg = reg.cuda()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "27d7caf5",
      "metadata": {
        "id": "27d7caf5"
      },
      "source": [
        "``pyepo.func.pairwiseLTR`` allows us to use a listwise learning to rank loss for training, which requires parameters:\n",
        "- ``optmodel``: an PyEPO optimization model\n",
        "- ``processes``: number of processors for multi-thread, 1 for single-core, 0 for all of cores\n",
        "- ``solve_ratio``: the ratio of new solutions computed during training\n",
        "- ``dataset``: a dataset to initialize the solution pool with. Usually this is simply the training set."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "72ff1275",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:16:27.478976953Z",
          "start_time": "2023-07-03T16:16:27.473935240Z"
        },
        "id": "72ff1275"
      },
      "outputs": [],
      "source": [
        "prltr = pyepo.func.pairwiseLTR(optmodel, solve_ratio=0.05, dataset=dataset_train)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2eb36db3",
      "metadata": {
        "id": "2eb36db3"
      },
      "source": [
        "##### Training"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4850ca91",
      "metadata": {
        "id": "4850ca91"
      },
      "source": [
        "To compute learning-to-rank loss, ``prltr`` requires $\\mathbf{\\hat{c}}$ and $\\mathbf{c}$."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "120e76ad",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:17:01.763318176Z",
          "start_time": "2023-07-03T16:16:32.516958317Z"
        },
        "id": "120e76ad"
      },
      "outputs": [],
      "source": [
        "loss_log, loss_log_regret = trainModel(reg, loss_func=prltr, method_name=\"ltr\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9ddef1bb",
      "metadata": {
        "id": "9ddef1bb"
      },
      "source": [
        "##### Learning Curve"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "a99fb844",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:17:46.308808781Z",
          "start_time": "2023-07-03T16:17:45.655774057Z"
        },
        "id": "a99fb844"
      },
      "outputs": [],
      "source": [
        "visLearningCurve(loss_log, loss_log_regret)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cb42db37",
      "metadata": {
        "id": "cb42db37"
      },
      "source": [
        "#### 4.7.3 Pointwise Learning To Rank"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ab4fef25",
      "metadata": {
        "id": "ab4fef25"
      },
      "source": [
        "An autograd module for pointwise learning to rank, where the goal is to learn an objective function that ranks a pool of feasible solutions correctly. For the pointwise LTR, the cost vector needs to be predicted from contextual data, and calculates the ranking scores of the items."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7b5081d9",
      "metadata": {
        "id": "7b5081d9"
      },
      "source": [
        "First of all, we initialize the predictor"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "b478099f",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:16:24.267717164Z",
          "start_time": "2023-07-03T16:16:18.452617457Z"
        },
        "id": "b478099f"
      },
      "outputs": [],
      "source": [
        "import torch\n",
        "# init model\n",
        "reg = LinearRegression()\n",
        "# cuda\n",
        "if torch.cuda.is_available():\n",
        "    reg = reg.cuda()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "05dea9c7",
      "metadata": {
        "id": "05dea9c7"
      },
      "source": [
        "``pyepo.func.pointwiseLTR`` allows us to use a listwise learning to rank loss for training, which requires parameters:\n",
        "- ``optmodel``: an PyEPO optimization model\n",
        "- ``processes``: number of processors for multi-thread, 1 for single-core, 0 for all of cores\n",
        "- ``solve_ratio``: the ratio of new solutions computed during training\n",
        "- ``dataset``: a dataset to initialize the solution pool with. Usually this is simply the training set"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6680ac57",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:16:27.478976953Z",
          "start_time": "2023-07-03T16:16:27.473935240Z"
        },
        "id": "6680ac57"
      },
      "outputs": [],
      "source": [
        "ptltr = pyepo.func.pointwiseLTR(optmodel, solve_ratio=0.05, dataset=dataset_train)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "03e921d1",
      "metadata": {
        "id": "03e921d1"
      },
      "source": [
        "##### Training"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1c51f232",
      "metadata": {
        "id": "1c51f232"
      },
      "source": [
        "To compute learning-to-rank loss, ``ptltr`` requires $\\mathbf{\\hat{c}}$ and $\\mathbf{c}$."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "543a0a05",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:17:01.763318176Z",
          "start_time": "2023-07-03T16:16:32.516958317Z"
        },
        "id": "543a0a05"
      },
      "outputs": [],
      "source": [
        "loss_log, loss_log_regret = trainModel(reg, loss_func=ptltr, method_name=\"ltr\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c6bc9a55",
      "metadata": {
        "id": "c6bc9a55"
      },
      "source": [
        "##### Learning Curve"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "35f1b5b7",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2023-07-03T16:17:46.308808781Z",
          "start_time": "2023-07-03T16:17:45.655774057Z"
        },
        "id": "35f1b5b7"
      },
      "outputs": [],
      "source": [
        "visLearningCurve(loss_log, loss_log_regret)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "81657621-4344-4c13-9f4f-2c6deed396f3",
      "metadata": {
        "id": "81657621-4344-4c13-9f4f-2c6deed396f3"
      },
      "outputs": [],
      "source": []
    }
  ],
  "metadata": {
    "colab": {
      "provenance": [],
      "gpuType": "T4"
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.11.10"
    },
    "accelerator": "GPU"
  },
  "nbformat": 4,
  "nbformat_minor": 5
}